﻿/* Luiz Felipe Netto - Trabalho 1: Rendering em Tempo Real
*  
* O objetivo deste trabalho é desenhar uma cena utilizando o pipeline antigo
* do OpenGL (2.1) fazendo uso apenas da função glLoadMatrix() para aplicar 
* transformações.
*
* A implementação elaborada tem como objetivo servir de base para este e outros trabalhos
* em OpenGL ao longo do semestre (2014/1), a qual será constantemente aperfeiçoada.
* O projeto completo (vs2013 sem libs) e atualizado pode ser acessado em: 
*   https://code.google.com/p/rendering-base/source/browse/
*
* O trabalho é composto dos seguintes itens:
* - Renderização de uma cena através do pipeline antigo fazendo uso de VBO;
* - Renderização de uma cena através do pipeline novo fazendo uso de iluminação ADS
*   por vértice (Gouraud) e por pixel (Phong). Ambas contém a implementação do halfway 
*   vector (Blinn-Phong) e a reflexão built-in;
* - Controle de FPS através da classe FramesControl;
* - Utização do mesmo buffer para desenhar no pipeline antigo e no novo.
*
* Breve descrição das classes:
* - A classe Camera é utilizada para definir os parametros de transformação da camera,
*   assim como a movimentação e rotação da camera do observador (ou 'player');
* - A classe PrimitiveObjects é utilizada como 'interface' para extender vários objetos básicos.
*   No momento somente as classes Grid e Sphere estão implementadas;
* - A classe Glsl foi baseada no livro OpenGL 4.0 Shading Cookbook de David Wolff
*/
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
//#include <glm\gtx\transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "callbacks.h"
#include "FramesControl.h"
#include "Glsl.h"
#include "Structs.h"
#include "Mouse.h"
#include "Camera.h"
#include "Grid.h"
#include "Sphere.h"

using namespace glm;

Mouse *mouse;
Camera *camera;
int width = 800;
int height = 600;

// General Init Functions 
GLFWwindow* glfwCfgInit (void);
void glfwCbSetup (GLFWwindow* window);
void openglCfgInit ();
void generalInit ();
void openglOldCfgIni ();

// Draw functions
void drawOldPipeline ();
void drawNewPipeline ();

//Scene 0 Objects
Glsl *p = NULL;
Grid *grid_0 = NULL;
Sphere *sphere_0 = NULL;
vec4 lightPos = vec4 (0.0f, 6.0f, 0.0f, 1.0f); //light source position
vec3 smColor = vec3 (1, 0, 0); //sphere material color;

int main (void)
{

   GLFWwindow* window = NULL;
   generalInit (); // Mouse, Camera and other global objects
   window = glfwCfgInit (); //Init GLFW and OGL Context
   openglCfgInit ();
   openglOldCfgIni ();
   glfwCbSetup (window);
   glfwSetCursorPos (window, width / 2.0, height / 2.0);

   FramesControl *fps = new FramesControl (60, window);
   //fps->setActive (false); //Debug

   p = new Glsl ("adsPerFrag.vert", NULL, "adsPerFrag.frag");
   //p = new Glsl ("adsPerVertex.vert", NULL, "adsPerVertex.frag");
   p->bindAttribLocation (0, "position");
   p->bindAttribLocation (1, "normal");

   p->link (); //Link() need to come after bindAttribLoc to make correct link betwen shaders (layout is arb on OGL 4.0)

   // Init Objects
   grid_0 = new Grid (1, 1, Vertex::type::P_AND_N);
   grid_0->init ();   

   sphere_0 = new Sphere (32, 32, Vertex::type::P_AND_N);
   sphere_0->init ();
   
   //MainLoop   
   while (!glfwWindowShouldClose (window)) {

      if (fps->update (width, height) == true) {

         glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
         glfwGetFramebufferSize (window, &width, &height);

         camera->update ();

         //drawOldPipeline ();
         drawNewPipeline ();

         glfwSwapBuffers (window);
         glfwPollEvents ();
      }
   }

   glfwDestroyWindow (window);
   glfwTerminate ();

   exit (EXIT_SUCCESS);
}

// Draw functions
void drawOldPipeline ()
{
   glViewport (0, 0, width, height);
   int vp[4]; glGetIntegerv (GL_VIEWPORT, vp);
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glMatrixMode (GL_PROJECTION);
   glLoadMatrixf (&camera->getProjection ()[0][0]);
   glLightfv (GL_LIGHT0, GL_POSITION, &lightPos[0]); //set light position
   glMatrixMode (GL_MODELVIEW);


   glColor3f (0.4f, 0.6f, 0.0f);                     //ground color
   
   mat4 tmp_srv = camera->getTransform () * scale (vec3(20.0f, 20.f, 20.0f)) * rotate (-90.0f, vec3 (1.0f, 0.f, 0.f));
   glLoadMatrixf (&tmp_srv[0][0]);
   grid_0->drawOld ();
   int i, j;
   float d = 2 * 10.f / 10;
   float p0 = -10.f + d / 2;

   glColor3fv (&smColor[0]); //spheres color
   for (i = 0; i < 10; i++) {
      for (j = 0; j < 10; j++) {
         mat4 tmp_stv = camera->getTransform () * translate (vec3 (p0 + i*d, 0.5f, p0 + j*d)) * scale (vec3 (0.5f, 0.5f, 0.5f));
         glLoadMatrixf (&tmp_stv[0][0]);
         sphere_0->drawOld ();
      }
   }

   /*
   //Draw light as sphere ~ Debug
   glColor3f (1.0f, 1.0f, 1.0f);
   mat4 tmp_stv = camera->getTransform () * translate (lightPos.x, lightPos.y, lightPos.z) * scale (0.2f, 0.2f, 0.2f);
   glLoadMatrixf (&mat4(1)[0][0]);
   sphere_0->drawOld (); */
   glLoadMatrixf (&camera->getTransform()[0][0]); //Load ModelView 
}

void drawNewPipeline ()
{
   p->useProgram (true);
   p->setUniform ("projection", camera->getProjection ());
   p->setUniform ("lsPos", camera->getTransform ()*lightPos); //light source position in eye coord   
  

   mat4 gTransform = translate(vec3(0.f,-0.1,0.f))*rotate (-90.f, vec3 (1.0f, 0.f, 0.f))*scale (vec3(20.0f, 20.0f, 20.0f));   

   p->setUniform ("modelView", camera->getTransform ()*gTransform);
   p->setUniform ("normalMatrix", inverse (transpose (mat3 (camera->getTransform ()*gTransform))));
   grid_0->draw ();
   
   float d = 2 * 10.f / 10;
   float p0 = -9.f + d / 2;
   for (int i = 0; i < 9; i++) {
      for (int j = 0; j < 9; j++) {
         mat4 transform = translate (vec3(p0 + i*d, 0.5f, p0 + j*d))*scale (vec3(0.5f, 0.5f, 0.5f));
         p->setUniform ("modelView", camera->getTransform ()*transform);
         p->setUniform ("normalMatrix", inverse (transpose (mat3 (camera->getTransform ()*transform))));
         sphere_0->draw ();
      }
   }
   p->useProgram (false);
}

// General Init Functions 
void generalInit ()
{
   camera = new Camera ((float)width, (float)height);
   mouse = new Mouse ();
}
GLFWwindow*  glfwCfgInit (void)
{
   GLFWwindow* window;

   glfwSetErrorCallback (errorCallback);

   if (glfwInit () == false) {
      exit (EXIT_FAILURE);
   }
   glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3); // Need to be at least OpenGL 3.3
   glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
   glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
   glfwWindowHint (GLFW_REFRESH_RATE, 5);

   window = glfwCreateWindow (width, height, "Simple example", NULL, NULL);
   if (!window) {
      glfwTerminate ();
      std::cout << "Error creating glfw window" << std::endl;
      system ("pause");
      exit (EXIT_FAILURE);
   }

   glfwMakeContextCurrent (window);
   glfwSetInputMode (window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
   glfwSetWindowPos (window, 20, 50);
   return window;
}
void glfwCbSetup (GLFWwindow* window)
{
   glfwSetCursorPosCallback (window, mouseCursorCallback);
   glfwSetMouseButtonCallback (window, mouseCallback);
   glfwSetKeyCallback (window, keyCallback);
   glfwSetErrorCallback (errorCallback);
   glfwSetWindowSizeCallback (window, windowSizeCallback);
   glfwSetFramebufferSizeCallback (window, framebufferSizeCallback);
}
void openglCfgInit ()
{
   glewExperimental = true;
   GLenum err = glewInit ();
   if (GLEW_OK != err) {
      fprintf (stderr, "Error: %s\n", glewGetErrorString (err));
   }
   fprintf (stdout, "Status: Using GLEW %s\n", glewGetString (GLEW_VERSION));

   const GLubyte *renderer = glGetString (GL_RENDERER);
   const GLubyte *vendor = glGetString (GL_VENDOR);
   const GLubyte *version = glGetString (GL_VERSION);
   const GLubyte *glslVersion =
   glGetString (GL_SHADING_LANGUAGE_VERSION);
   GLint major, minor;
   glGetIntegerv (GL_MAJOR_VERSION, &major);
   glGetIntegerv (GL_MINOR_VERSION, &minor);
   printf ("GL Vendor : %s\n", vendor);
   printf ("GL Renderer : %s\n", renderer);
   printf ("GL Version (string) : %s\n", version);
   printf ("GL Version (integer) : %d.%d\n", major, minor);
   printf ("GLSL Version : %s\n", glslVersion);

   glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
   glEnable (GL_DEPTH_TEST);
   glEnable (GL_CULL_FACE);
   glCullFace (GL_BACK);
   glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);

}
void openglOldCfgIni ()
{
   GLfloat white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
   glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
   glEnable (GL_LIGHTING);
   glEnable (GL_LIGHT0);
   glEnable (GL_COLOR_MATERIAL);
   glEnable (GL_RESCALE_NORMAL); //Avoid normal scale problem
   glColorMaterial (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
   glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, white);
   glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 50.0);
   glLightModeli (GL_LIGHT_MODEL_LOCAL_VIEWER, 1);

}

// Callback Functions 
static void errorCallback (int error, const char* description)
{
   fputs (description, stderr);
}

//Just a temporary approach
static void keyCallback (GLFWwindow* window, int key, int scancode, int action, int mods)
{
   bool update = true;
   switch (key) {
      case GLFW_KEY_ESCAPE:
         if (action == GLFW_RELEASE) {
            glfwSetWindowShouldClose (window, GL_TRUE);
         }
         break;
      case GLFW_KEY_W:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->FWD);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->FWD);
         }
         break;
      case GLFW_KEY_S:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->BWD);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->BWD);
         }
         break;
      case GLFW_KEY_A:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->STL);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->STL);
         }
         break;
      case GLFW_KEY_D:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->STR);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->STR);
         }
         break;
      case GLFW_KEY_SPACE:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->UP);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->UP);
         }
         break;
      case GLFW_KEY_X:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->DOWN);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->DOWN);
         }
         break;
      case GLFW_KEY_E:
         //
         break;
      case GLFW_KEY_Q:
         //
         break;
      default:
         update = false;
         break;
   }
   if (update == false) {
      if (action == GLFW_RELEASE) {
         printf ("%c Released\n", key);
      }
      else {
         printf ("%c Pressed\n", key);
      }
   }
   else {
      //printf (" %c: Pos( %.2f, %.2f, %.2f )\n", key, camera->getPosition ().x, camera->getPosition ().y, camera->getPosition ().z);
   }
}
static void mouseCallback (GLFWwindow* window, int button, int action, int mods)
{
   mouse->setButton (button, action, mods);
   printf ("(%.2lf, %.2lf) bt:%d act:%d mod:%d\n", mouse->getPos ().x, mouse->getPos ().y, mouse->getButton (), mouse->getAction (), mouse->getMods ());
}
static void mouseCursorCallback (GLFWwindow* window, double px, double py)
{
   //printf ("x: %.2lf y: %.2lf\n", px, py);
   mouse->setPos (px, py);
   camera->setMouse (vec2 (px, py));
}
static void windowSizeCallback (GLFWwindow *window, int width, int height)
{
   std::cout << "Entrou " << width << " " << height << std::endl;
   //glfwSetWindowSize (window, width, height);
   camera->setDimension ((float)width, (float)height);
}
static void framebufferSizeCallback (GLFWwindow* window, int width, int height)
{
   glViewport (0, 0, width, height);
}
